package config

import (
	"errors"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"github.com/jinzhu/gorm"
	"log"
	"time"
)
// engineMap 每个数据库连接pool就是一个db引擎
var engineMap = map[string]*gorm.DB{}

type DbConf struct {
	Ip           string
	Port         int //默认3306
	User         string
	Password     string
	Database     string
	Charset      string //字符集 utf8mb4 支持表情符号
	Collation    string //整理字符集 utf8mb4_unicode_ci
	MaxIdleConns int    //空闲pool个数
	MaxOpenConns int    //最大open connection个数
	// sets the maximum amount of time a connection may be reused.
	// 设置连接可以重用的最大时间
	// 给db设置一个超时时间，时间小于数据库的超时时间
	MaxLifetime int64 //数据库超时时间，单位s

	ParseTime  bool
	Loc        string   //时区字符串 Local,PRC
	engineName string   //当前数据库连接句柄标识
	dbObj      *gorm.DB //当前数据库连接句柄
	SqlCmd     bool     //sql语句是否输出到终端,true输出到终端，生产环境建议关闭，因为log会加锁
	UsePool    bool     //当前db实例是否采用db连接池,默认不采用，如采用请求配置该参数
}

func (conf *DbConf) initDb() error {
	if conf.Ip == "" {
		conf.Ip = "127.0.0.1"
	}

	if conf.Port == 0 {
		conf.Port = 3306
	}

	if conf.Charset == "" {
		conf.Charset = "utf8mb4"
	}

	if conf.Collation == "" {
		conf.Collation = "utf8mb4_unicode_ci"
	}

	if conf.Loc == "" {
		conf.Loc = "Local"
	}

	db, err := gorm.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&collation=%s&parseTime=%v&loc=%s",
		conf.User, conf.Password, conf.Ip, conf.Port, conf.Database,
		conf.Charset, conf.Collation, conf.ParseTime, conf.Loc))
	if err != nil { //数据库连接错误
		return err
	}

	//将sql打印到终端
	if conf.SqlCmd {
		db.LogMode(true)
	}

	//设置连接池
	if conf.UsePool {
		db.DB().SetMaxIdleConns(conf.MaxIdleConns)
		db.DB().SetMaxOpenConns(conf.MaxOpenConns)
	}

	// 设置连接可以重用的最大时间
	// 给db设置一个超时时间，时间小于数据库的超时时间
	if conf.MaxLifetime > 0 {
		db.DB().SetConnMaxLifetime(time.Duration(conf.MaxLifetime) * time.Second)
	}

	conf.dbObj = db

	// log.Println("current dbObj: ",db)

	return nil
}

//创建当前数据库db对象，并非连接，在使用的时候才会真正建立db连接
func (conf *DbConf) SetDbObj() error {
	err := conf.initDb()
	if err != nil {
		log.Println("set db engine error: ", err)
		return err
	}

	return nil
}

// SetDbPool 设置db pool连接池
func (conf *DbConf) SetDbPool() error {
	conf.UsePool = true
	return conf.SetDbObj()
}

// SetEngineName 给当前数据库指定engineName
func (conf *DbConf) SetEngineName(name string) error {
	if name == "" {
		return errors.New("current engine name is empty!")
	}

	if conf.dbObj == nil {
		return errors.New("current " + name + " db engine not be initDb")
	}

	conf.engineName = name
	engineMap[conf.engineName] = conf.dbObj

	return nil
}

// ShortConnect 建立短连接，用完需要调用Close()进行关闭连接，释放资源，否则就会出现too many connection
func (conf *DbConf) ShortConnect() error {
	conf.UsePool = false
	err := conf.initDb()
	if err != nil {
		log.Println("set db engine error: ", err)
		return err
	}

	return nil
}
// Close 关闭当前数据库连接
// 一般建议，将当前db engine close函数放在main/init关闭连接就可以
func (conf *DbConf) Close() error {
	if conf.dbObj == nil {
		return nil
	}

	if err := conf.Db().Close(); err != nil {
		log.Println("close db error: ", err.Error())
		return err
	}

	//如果设置了engineName就需要把连接句柄对象从map中删除
	if _, ok := engineMap[conf.engineName]; ok {
		delete(engineMap, conf.engineName)
	}

	return nil
}

// Db 返回当前db对象
func (conf *DbConf) Db() *gorm.DB {
	return conf.dbObj
}

// ========================辅助函数===============
// GetDbObj 从db pool获取一个数据库连接句柄
//根据数据库连接句柄name获取指定的连接句柄
func GetDbObj(name string) (*gorm.DB, error) {
	if _, ok := engineMap[name]; ok {
		return engineMap[name], nil
	}

	return nil, errors.New("get db obj failed")
}
// CloseAllDb 由于gorm db.Close()是关闭当前连接，一般建议如下函数放在main/init关闭连接就可以
func CloseAllDb() {
	for name, db := range engineMap {
		if err := db.Close(); err != nil {
			log.Println("close db error: ", err.Error())
			continue
		}

		delete(engineMap, name) //销毁连接句柄标识
	}
}

// CloseDbByName 关闭指定name的db engine
func CloseDbByName(name string) error {
	if _, ok := engineMap[name]; ok {
		if err := engineMap[name].Close(); err != nil {
			log.Println("close db error: ", err.Error())
			return err
		}

		delete(engineMap, name) //销毁连接句柄标识
	}

	return errors.New("current dbObj not exist")
}

